import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import javax.swing.*;

class Range {
	Range(int mi, int ma, float z, float z2) {
	        min = mi;
	        max = ma;
	        zValueMin = z;
	        zValueMax = z2;
	    }
	    int min;
	    int max;
	    float zValueMin;
	    float zValueMax;
	}
class Polygon {
	Polygon() {
		
	}
	int numberOfPolygons;
}
@SuppressWarnings("serial")
public class ProjectTwoMilestoneOne extends JPanel implements KeyListener {
	int width;
	int height;
	int imageSize;
	int[] pixels;
	int rotateXValue;
	int rotateYValue;
	int left = 0;
	int right = 0;
	int top = 0;
	int bottom = 0;
	int near = 0;
	int far = 0;
	private final float[][] identityMatrix = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } };
	float[][] currentTransformationMatrix;
	float[][] projectionMatrix;
	float[][] lookAtMatrix;
	static String file = "";
	String interactiveRotation = "";
	private ArrayList<String> transformations;
	private Range[] coords;
	private int[] color;
	private float[][][] zBuffer;
	
	ProjectTwoMilestoneOne() {
		setPreferredSize(new Dimension(512, 512));
		setFocusable(true);
		addKeyListener(this);
	}
	void drawPixel(int x, int y, int r, int g, int b) {
		pixels[(height - y - 1) * width * 3 + x * 3] = r;
		pixels[(height - y - 1) * width * 3 + x * 3 + 1] = g;
		pixels[(height - y - 1) * width * 3 + x * 3 + 2] = b;
	}
	void createImage() throws IOException {
		transformations = new ArrayList<String>();
		if(file.equals("")) {
			Scanner Scanscan = new Scanner(System.in);
			System.out.print("Input filename:");
			file = Scanscan.nextLine();
			Scanscan.close();
		}
		BufferedReader br = new BufferedReader(new FileReader(file));
		try {
			currentTransformationMatrix = new float[4][4];
			projectionMatrix = new float[4][4];
			lookAtMatrix = new float[4][4];
			float r = 0;
			float g = 0;
			float b = 0;
			float rotateValueX = 0;
			float rotateValueY = 0;
			final float plusPointFive = (float) 0.5;
			final float negaPointFive = (float) -0.5;
			String line = br.readLine();
			color = new int[3];
			while (line != null) {
				String[] tokens = new String[line.split(" ").length];
				tokens = line.split(" ");
				tokens = clean(tokens);
				if (!transformations.isEmpty() && (tokens[0].equals("LOAD_IDENTITY_MATRIX") || tokens[0].equals("FRUSTUM") || tokens[0].equals("ORTHO"))) {
					transformations.clear();
				}
				switch(tokens[0]) {
					case "DIM":
						setDimensions(Integer.parseInt(tokens[2]), Integer.parseInt(tokens[1]));
						doBackground();
						break;
					case "RGB":
						color[0] = (int) (Float.parseFloat(tokens[1])*255);
						color[1] = (int) (Float.parseFloat(tokens[2])*255);
						color[2] = (int) (Float.parseFloat(tokens[1])*255);
						r = Float.parseFloat(tokens[1]);
						g = Float.parseFloat(tokens[2]);
						b = Float.parseFloat(tokens[3]);
						shading(tokens, r, g, b);
						break;
					case "LOAD_IDENTITY_MATRIX":
						identityMatrix();
						break;
					case "FRUSTUM":
						frustum(tokens);
						break;
					case "ORTHO":
						ortho(tokens);
						break;
					case "LOOKAT":
						lookAt(tokens);
						break;
					case "TRANSLATE":
						translate(tokens);
						break;
					case "SCALE":
						scale(tokens);
						break;
					case "ROTATEX":
						rotateValueX = Float.parseFloat(tokens[1]);
						rotateX(tokens);
						break;
					case "ROTATEY":
						rotateValueY = Float.parseFloat(tokens[1]);
						rotateY(tokens);
						break;
					case "ROTATEZ":
						rotateZ(tokens);
						break;
					case "LINE":
						interactiveRotation(rotateValueX, rotateValueY);
						float[][] linePoint1 = transformPoint(Float.parseFloat(tokens[1]),Float.parseFloat(tokens[2]),Float.parseFloat(tokens[3]));
						float[][] linePoint2 = transformPoint(Float.parseFloat(tokens[4]),Float.parseFloat(tokens[5]),Float.parseFloat(tokens[6]));
						drawLine(worldToScreenX(linePoint1[0][0]), 
				                            worldToScreenY(linePoint1[1][0]),
				                            worldToScreenZ(linePoint1[2][0]), 
				                            worldToScreenY(linePoint2[0][0]),
		                            		worldToScreenX(linePoint2[1][0]), 
		                           			worldToScreenZ(linePoint2[2][0]),
		                           			r, g, b);
						interactiveRotationSetup(rotateValueX, rotateValueY);
						break;
					case "TRI":
						interactiveRotation(rotateValueX, rotateValueY);
						float[][] triPoint1 = transformPoint(Float.parseFloat(tokens[1]),Float.parseFloat(tokens[2]),Float.parseFloat(tokens[3]));
						float[][] triPoint2 = transformPoint(Float.parseFloat(tokens[4]),Float.parseFloat(tokens[5]),Float.parseFloat(tokens[6]));
						float[][] triPoint3 = transformPoint(Float.parseFloat(tokens[7]),Float.parseFloat(tokens[8]),Float.parseFloat(tokens[9]));
			                        drawTriangle(worldToScreenX(triPoint1[0][0]), 
					                            worldToScreenY(triPoint1[1][0]),
					                            worldToScreenZ(triPoint1[2][0]), 
					                            worldToScreenY(triPoint2[0][0]),
					                            worldToScreenX(triPoint2[1][0]), 
					                            worldToScreenZ(triPoint2[2][0]),
					                            worldToScreenX(triPoint3[0][0]), 
					                            worldToScreenY(triPoint3[1][0]),
					                            worldToScreenZ(triPoint3[2][0]),
									r, g, b);
						interactiveRotationSetup(rotateValueX, rotateValueY);			
						break;
					case "WIREFRAME_CUBE":
						interactiveRotation(rotateValueX, rotateValueY);
						float[][] wireFrameCubePoint1 = transformPoint(plusPointFive,plusPointFive,plusPointFive);
						float[][] wireFrameCubePoint2 = transformPoint(negaPointFive,plusPointFive,plusPointFive);
						float[][] wireFrameCubePoint3 = transformPoint(plusPointFive,negaPointFive,plusPointFive);
						float[][] wireFrameCubePoint4 = transformPoint(negaPointFive,negaPointFive,plusPointFive);
						
						float[][] wireFrameCubePoint5 = transformPoint(plusPointFive,plusPointFive,negaPointFive);
						float[][] wireFrameCubePoint6 = transformPoint(negaPointFive,plusPointFive,negaPointFive);
						float[][] wireFrameCubePoint7 = transformPoint(plusPointFive,negaPointFive,negaPointFive);
						float[][] wireFrameCubePoint8 = transformPoint(negaPointFive,negaPointFive,negaPointFive);
			                        drawWireFrameCube(wireFrameCubePoint1, wireFrameCubePoint2, wireFrameCubePoint3, wireFrameCubePoint4,
			                     		       wireFrameCubePoint5, wireFrameCubePoint6, wireFrameCubePoint7, wireFrameCubePoint8,
			                     		       r, g, b);
						interactiveRotationSetup(rotateValueX, rotateValueY);
						break;
					case "SOLID_CUBE":
						interactiveRotation(rotateValueX, rotateValueY);
						float[][] solidCubePoint1 = transformPoint(plusPointFive,plusPointFive,plusPointFive);
						float[][] solidCubePoint2 = transformPoint(negaPointFive,plusPointFive,plusPointFive);
						float[][] solidCubePoint3 = transformPoint(plusPointFive,negaPointFive,plusPointFive);
						float[][] solidCubePoint4 = transformPoint(negaPointFive,negaPointFive,plusPointFive);
						
						float[][] solidCubePoint5 = transformPoint(plusPointFive,plusPointFive,negaPointFive);
						float[][] solidCubePoint6 = transformPoint(negaPointFive,plusPointFive,negaPointFive);
						float[][] solidCubePoint7 = transformPoint(plusPointFive,negaPointFive,negaPointFive);
						float[][] solidCubePoint8 = transformPoint(negaPointFive,negaPointFive,negaPointFive);
						 drawSolidCube(solidCubePoint1, solidCubePoint2, solidCubePoint3, solidCubePoint4,
								 solidCubePoint5, solidCubePoint6, solidCubePoint7, solidCubePoint8,
								 r, g, b);
						interactiveRotationSetup(rotateValueX, rotateValueY);
						break;
					default:
						break;
						
				} 
				line = br.readLine();

			}
		} finally {
			br.close();
		}
	}
	public static String[] clean(final String[] v) {
		  int r, w, n = r = w = v.length;
		  while (r > 0) {
		    final String s = v[--r];
		    if (!(s.equals("null") || s.equals(""))) {
		      v[--w] = s;
		    }
		  }
		  final String[] c = new String[n -= w];
		  System.arraycopy(v, w, c, 0, n);
		  return c;
	}
	/**
	 *  Sets the dimensions of the panel to the given size.
	 * @param width The width.
	 * @param height The height.
	 */
	private void setDimensions(int width, int height) {
	       this.width = width;
	       this.height = height;
	       imageSize = width * height;
		pixels = new int[imageSize * 3];
		currentTransformationMatrix = identityMatrix;
	       zBuffer = new float[width][height][1];
	}
	private void doBackground() {
		for(int x = 0; x < width; x++) {
			for(int y = 0; y < height; y++) {
				drawPixel(x, y, 255,255,255);
			}
		}
		for(int i = 0; i < width; i++) {
			for(int x = 0; x < height; x++) {
				zBuffer[i][x][0] = Float.MIN_VALUE;
			}
		}
	}
	private void identityMatrix() {
		transformations.add("LOAD_IDENTITY_MATRIX");
		projectionMatrix = identityMatrix;
		currentTransformationMatrix = identityMatrix;
		lookAtMatrix = identityMatrix;
	}
	private void frustum(String[] tokens) {
		transformations.add("FRUSTUM");
		float frustumLeft = Float.parseFloat(tokens[1])+left;
		float frustumRight = Float.parseFloat(tokens[2])+right;
		float frustumBottom = Float.parseFloat(tokens[3])+top;
		float frustumTop = Float.parseFloat(tokens[4])+bottom;
		float frustumNear = Float.parseFloat(tokens[5])+near;
		float frustumFar = Float.parseFloat(tokens[6])+far;
		
		float[][] frustum = { { 2*frustumNear/(frustumRight-frustumLeft), 0, (frustumRight+frustumLeft)/(frustumRight-frustumLeft), 0 }, 
				         { 0, 2*frustumNear/(frustumTop-frustumBottom), (frustumTop+frustumBottom)/(frustumTop-frustumBottom), 0 }, 
				         { 0, 0, -(frustumFar+frustumNear)/(frustumFar-frustumNear), -2*frustumFar*frustumNear/(frustumFar-frustumNear) }, 
				         { 0, 0, -1, 0 } };
		projectionMatrix = frustum;
		currentTransformationMatrix = identityMatrix;
	}
	private void ortho(String[] tokens) {
		transformations.add("ORTHO");
		float orthoLeft = Float.parseFloat(tokens[1])+left;
		float orthoRight = Float.parseFloat(tokens[2])+right;
		float orthoBottom = Float.parseFloat(tokens[3])+bottom;
		float orthoTop = Float.parseFloat(tokens[4])+top;
		float orthoNear = Float.parseFloat(tokens[5])+near;
		float orthoFar = Float.parseFloat(tokens[6])+far;
		
		float[][] ortho = { { 2/(orthoRight-orthoLeft), 0, -(orthoRight+orthoLeft)/(orthoRight-orthoLeft), 0 }, 
				    { 0, 2/(orthoTop-orthoBottom), -(orthoTop+orthoBottom)/(orthoTop-orthoBottom), 0 }, 
				    { 0, 0, -2/(orthoNear-orthoFar), -(orthoFar*orthoNear)/(orthoFar-orthoNear) }, 
				    { 0, 0, 0, 1 } };
		projectionMatrix = ortho;
		currentTransformationMatrix = identityMatrix;
	}
	private void lookAt(String[] tokens) {
		transformations.add("LOOKAT");
		float eyeX = Float.parseFloat(tokens[1]);
		float eyeY = Float.parseFloat(tokens[2]);
		float eyeZ = Float.parseFloat(tokens[3]);
		float atX = Float.parseFloat(tokens[4]);
		float atY = Float.parseFloat(tokens[5]);
		float atZ = Float.parseFloat(tokens[6]);
		float upX = Float.parseFloat(tokens[7]);
		float upY = Float.parseFloat(tokens[8]);
		float upZ = Float.parseFloat(tokens[9]);
		float[][] eye = {{eyeX}, {eyeY}, {eyeZ}, {1}};
		float[][] at = {{atX}, {atY}, {atZ}, {1}};
		float[][] up = {{upX}, {upY}, {upZ}, {0}};
		//used to create matrix: order of rows are u, v, n, t
		float[][] n = {{eye[0][0]-at[0][0]}, {eye[1][0]-at[1][0]}, {eye[2][0]-at[2][0]}, {0}};
		n = normalize(n);
		float[][] u = cross(up, n);
		u = normalize(u);
		float[][] v = cross(n, u);
		v = normalize(v);
		float[][] t = {{0}, {0}, {0}, {1}};
		//matrix created by u, v, n and t
		float[][] c = {{u[0][0], u[1][0], u[2][0], u[3][0]}, 
				  {v[0][0], v[1][0], v[2][0], v[3][0]}, 
				  {n[0][0], n[1][0], n[2][0], n[3][0]}, 
				  {t[0][0], t[1][0], t[2][0], t[3][0]}};
		float[][] multiplied = {{ 1, 0, 0, 0-eyeX }, 
					    { 0, 1, 0, 0-eyeY }, 
					    { 0, 0, 1, 0-eyeZ }, 
					    { 0, 0, 0, 1 }};
		float[][] lookAt = multiplyMatrix(c, multiplied);
		
		lookAtMatrix = lookAt;
		currentTransformationMatrix = identityMatrix;
	}
	private float[][] normalize(float[][] n) {
		float magnitude = (float) Math.sqrt(((n[0][0]*n[0][0])+(n[1][0]*n[1][0])+(n[2][0]*n[2][0])));
		float x = n[0][0]/magnitude;
		float y = n[1][0]/magnitude;
		float z = n[2][0]/magnitude;
		float[][] returned = {{x}, {y}, {z}, {0}};
		return returned;
	}
	private float[][] cross(float[][] n, float[][] u) {
		float[][] crossMatrix = {{(n[1][0]*u[2][0])-(n[2][0]*u[1][0])},
					     {(n[2][0]*u[0][0])-(n[0][0]*u[2][0])},
					     {(n[0][0]*u[1][0])-(n[1][0]*u[0][0])},
					     {0}};
		return crossMatrix;
	}
	private void shading(String[] tokens, double r, double g, double b) {
		
	}
	private void translate(String[] tokens) {
		transformations.add("TRANSLATE");
		float[][] translate = { { 1, 0, 0, Float.parseFloat(tokens[1]) }, 
					    { 0, 1, 0, Float.parseFloat(tokens[2]) }, 
					    { 0, 0, 1, Float.parseFloat(tokens[3]) }, 
					    { 0, 0, 0, 1 } };
		currentTransformationMatrix = multiplyMatrix(translate, currentTransformationMatrix);
	}
	private void scale(String[] tokens) {
		transformations.add("SCALE");
		float[][] scale = { { Float.parseFloat(tokens[1]), 0, 0, 0 }, 
					{ 0, Float.parseFloat(tokens[2]), 0, 0 }, 
					{ 0, 0, Float.parseFloat(tokens[3]), 0 },
					{ 0, 0, 0, 1 } };
		currentTransformationMatrix = multiplyMatrix(scale, currentTransformationMatrix);
	}
	private void rotateX(String[] tokens) {
		transformations.add("ROTATEX");
		float[][] rotateX = { { 1, 0, 0, 0 }, 
				  { 0,(float) Math.cos(Math.toRadians(Integer.parseInt(tokens[1])+rotateXValue)), (float) (0-Math.sin(Math.toRadians(Integer.parseInt(tokens[1])+rotateXValue))), 0 }, 
				  { 0, (float) Math.sin(Math.toRadians(Integer.parseInt(tokens[1])+rotateXValue)), (float) Math.cos(Math.toRadians(Integer.parseInt(tokens[1])+rotateXValue)), 0 }, 
				  { 0, 0, 0, 1 } };
		currentTransformationMatrix = multiplyMatrix(rotateX, currentTransformationMatrix);
	}
	private void rotateY(String[] tokens) {
		transformations.add("ROTATEY");
		float[][] rotateY = { { (float) Math.cos(Math.toRadians(Integer.parseInt(tokens[1])+rotateYValue)), 0, (float) Math.sin(Math.toRadians(Integer.parseInt(tokens[1])+rotateYValue)), 0 }, 
				  { 0, 1, 0, 0 }, 
				  { (float) (0-Math.sin(Math.toRadians(Integer.parseInt(tokens[1])+rotateYValue))), 0, (float) Math.cos(Math.toRadians(Integer.parseInt(tokens[1])+rotateYValue)), 0 }, 
				  { 0, 0, 0, 1 } };
		currentTransformationMatrix = multiplyMatrix(rotateY, currentTransformationMatrix);
	}
	private void rotateZ(String[] tokens) {
		transformations.add("ROTATEZ");
		float[][] rotatez = { { (float) Math.cos(Math.toRadians(Integer.parseInt(tokens[1]))), (float) (0-Math.sin(Math.toRadians(Integer.parseInt(tokens[1])))), 0, 0 }, 
					  { (float) Math.cos(Math.toRadians(Integer.parseInt(tokens[1]))), (float) Math.cos(Math.toRadians(Integer.parseInt(tokens[1]))), 0, 0 }, 
					  { 0, 0, 1, 0 }, 
					  { 0, 0, 0, 1 } };
		currentTransformationMatrix = multiplyMatrix(rotatez, currentTransformationMatrix);
	}
	public static float[][] multiplyMatrix(float[][] A, float[][] point1) {
		
		int aRows = A.length;
		int aColumns = A[0].length;
		int bRows = point1.length;
		int bColumns = point1[0].length;
		
		if (aColumns != bRows) {
			throw new IllegalArgumentException("A:Rows: " + aColumns + " did not match B:Columns " + bRows + ".");
		}
		
		float[][] C = new float[aRows][bColumns];
		
		for (int i = 0; i < aRows; i++) { // aRow
			for (int j = 0; j < bColumns; j++) { // bColumn
				for (int k = 0; k < aColumns; k++) { // aColumn
					C[i][j] += A[i][k] * point1[k][j];
				}
			}
		}
		return C;
	}
	private float[][] transformPoint(float x, float y, float z) {
		float[][] point = {{x},{y},{z},{1}};
		point = multiplyMatrix(currentTransformationMatrix, point);
//		point = multiplyMatrix(lookAtMatrix, point);
//		point = multiplyMatrix(projectionMatrix, point);		
		return point;
	}
	private void interactiveRotation(double rotateValueX, double rotateValueY) {
		if(!(transformations.contains("ROTATEX")) || !(transformations.contains("ROTATEY"))) {
			if(!(transformations.contains("ROTATEX"))) {
				checkRotatedX(rotateValueX);
			}
			if(!(transformations.contains("ROTATEY"))) {
				checkRotatedY(rotateValueY);
			}
		}
	}
	private void checkRotatedY(double rotateValueY) {
		float[][] rotatey = { { (float) Math.cos(Math.toRadians(rotateValueY+rotateYValue)), 0, (float) Math.sin(Math.toRadians(rotateValueY+rotateYValue)), 0 }, 
				  { 0, 1, 0, 0 }, 
				  { (float) (0-Math.sin(Math.toRadians(rotateValueY+rotateYValue))), 0, (float) Math.cos(Math.toRadians(rotateValueY+rotateYValue)), 0 }, 
				  { 0, 0, 0, 1 } };
	currentTransformationMatrix = multiplyMatrix(rotatey, currentTransformationMatrix);
	}
	private void checkRotatedX(double rotateValueX) {
		float[][] rotatex = { { 1, 0, 0, 0 }, 
				  { 0, (float) Math.cos(Math.toRadians(rotateValueX+rotateXValue)), (float) (0-Math.sin(Math.toRadians(rotateValueX+rotateXValue))), 0 }, 
				  { 0, (float) Math.sin(Math.toRadians(rotateValueX+rotateXValue)), (float) Math.cos(Math.toRadians(rotateValueX+rotateXValue)), 0 }, 
				  { 0, 0, 0, 1 } };
	currentTransformationMatrix = multiplyMatrix(rotatex, currentTransformationMatrix);
	}
	private void interactiveRotationSetup(double rotatedValueX, double rotatedValueY) {
		if(!(transformations.contains("ROTATEX")) || !(transformations.contains("ROTATEY"))) {
			currentTransformationMatrix = identityMatrix;
			if((transformations.contains("ROTATEX"))) {
				currentTransformationMatrix = identityMatrix;
				float[][] rotatex = { { 1, 0, 0, 0 }, 
						  { 0, (float) Math.cos(Math.toRadians(rotatedValueX+rotateXValue)), (float) (0-Math.sin(Math.toRadians(rotatedValueX+rotateXValue))), 0 }, 
						  { 0, (float) Math.sin(Math.toRadians(rotatedValueX+rotateXValue)), (float) Math.cos(Math.toRadians(rotatedValueX+rotateXValue)), 0 }, 
						  { 0, 0, 0, 1 } };
			currentTransformationMatrix = multiplyMatrix(rotatex, currentTransformationMatrix);
			}
			if((transformations.contains("ROTATEY"))) {
				float[][] rotatey = { { (float) Math.cos(Math.toRadians(rotatedValueY+rotateYValue)), 0, (float) Math.sin(Math.toRadians(rotatedValueY+rotateYValue)), 0 }, 
						  { 0, 1, 0, 0 }, 
						  { (float) (0-Math.sin(Math.toRadians(rotatedValueY+rotateYValue))), 0, (float) Math.cos(Math.toRadians(rotatedValueY+rotateYValue)), 0 }, 
						  { 0, 0, 0, 1 } };
			currentTransformationMatrix = multiplyMatrix(rotatey, currentTransformationMatrix);
			} 
		}
	}
	    /**
	     * Converts an X world coordinate to a screen coordinate.
	     * 
	     * @param x_world Coordinate to convert.
	     * @return The screen coordinate of the given world coordinate as an int.
	     */
	    private int worldToScreenX(float x_world) {
	        return Math.round((width-1.0f) * (x_world + 1.0f) / 2.0f);
	    }
	    
	    /**
	     * Converts a Y world coordinate to a screen coordinate.
	     * 
	     * @param y_world Coordinate to convert.
	     * @return The screen coordinate of the given world coordinate as an int.
	     */
	    private int worldToScreenY(float y_world) {
	        return Math.round((height-1.0f) * (y_world + 1.0f) / 2.0f);
	    }
	    /**
	     * Converts a Y world coordinate to a screen coordinate.
	     * 
	     * @param y_world Coordinate to convert.
	     * @return The screen coordinate of the given world coordinate as an int.
	     */
	    private float worldToScreenZ(float z_world) {
	        return z_world;
	    }	    /**
	     * Draws a line given the coordinates.
	     * 
	     * @param x1 X coordinate of the first point.
	     * @param y1 Y coordinate of the first point.
	     * @param x2 X coordinate of the second point.
	     * @param y2 Y coordinate of the second point.
	     */
	    private void drawLine(int sx1, int sy1, float sz1, int sx2, int sy2, float sz2, double r, double g, double b) {
		    drawLine(sx1, sy1, sz1, sx2, sy2, sz2, false, r, g, b);
	    }
	    /**
	     * Draws a triangle with the given coordinates.
	 * @param b 
	 * @param g 
	 * @param r 
	     * 
	     * @param x0 X coordinate of the first point.
	     * @param y0 Y coordinate of the first point.
	     * @param x1 X coordinate of the second point.
	     * @param y1 Y coordinate of the second point.
	     * @param x2 X coordinate of the third point.
	     * @param y2 Y coordinate of the third point.
	     */
	    private void drawTriangle(int sx0, int sy0, float sz0, int sx1, int sy1, float sz1, int sx2, int sy2, float sz2, double r, double g, double b) {
	      
	        // initialize array to keep track of all pixel positions
	        coords = new Range[height];
//	        drawLine(sx0, sy0,sz0, sx1, sy1,sz1, false, r, g, b);
//	        drawLine(sx0, sy0,sz0, sx2, sy2,sz2, false, r, g, b);
//	        drawLine(sx1, sy1,sz1, sx2, sy2, sz2,false, r, g, b);
	        drawLine(sx0, sy0,sz0, sx1, sy1,sz1, true, r, g, b);
	        drawLine(sx0, sy0,sz0, sx2, sy2,sz2, true, r, g, b);
	        drawLine(sx1, sy1,sz1, sx2, sy2, sz2,true, r, g, b);
	      
	        int largest_y = findLargestNum(sy0, sy1, sy2);  
	        int smallest_y = findSmallestNum(sy0, sy1, sy2);
	        
	        for (int i = smallest_y; i <= largest_y; i++) {
	            // only draw the lines that we've covered/initialized
	            if (coords[i] != null) {
	       	     int x1 = coords[i].min;
	       	     int x2 = coords[i].max;
	       	     float z1 = zBuffer[x1][i][0];
	       	     float z2 = zBuffer[x2][i][0];
	                drawLine(coords[i].min, i, z1, coords[i].max, i, z2, r, g, b);
	            }  
	        }  
	    }
	    /**
	     * Finds and returns the largest number out of the three numbers given.
	     * 
	     * @param sy0 The first number.
	     * @param sy1 The second number.
	     * @param sy2 The third number.
	     * @return The largest number of the three given numbers.
	     */
	   private int findLargestNum(int sy0, int sy1, int sy2) {
	      return Math.max(Math.max(sy0, sy1),Math.max(sy1, sy2));
	   }

	   /**
	    * Finds and returns the largest number out of the three numbers given.
	    * 
	    * @param sy0 The first number.
	    * @param sy1 The second number.
	    * @param sy2 The third number.
	    * @return The largest number of the three given numbers.
	    */
	   private int findSmallestNum(int sy0, int sy1, int sy2) {
	      return Math.min(Math.min(sy0, sy2), Math.min(sy1, sy2)); 
	   }

		private void drawWireFrameCube(float[][] wireFrameCubePoint1, float[][] wireFrameCubePoint2, float[][] wireFrameCubePoint3,
				float[][] wireFrameCubePoint4, float[][] wireFrameCubePoint5, float[][] wireFrameCubePoint6, float[][] wireFrameCubePoint7,
				float[][] wireFrameCubePoint8, float r, float g, float b) {
			//front
			drawSquareLine(wireFrameCubePoint1, wireFrameCubePoint2, wireFrameCubePoint3, wireFrameCubePoint4, r, g ,b);
			//back
			drawSquareLine(wireFrameCubePoint5, wireFrameCubePoint6, wireFrameCubePoint7, wireFrameCubePoint8, r, g, b);
			//sides
			drawSquareLine(wireFrameCubePoint1, wireFrameCubePoint5, wireFrameCubePoint3, wireFrameCubePoint7, r, g, b);
			drawSquareLine(wireFrameCubePoint2, wireFrameCubePoint6, wireFrameCubePoint4, wireFrameCubePoint8, r, g, b);			
		}
		private void drawSquareLine(float[][] first, float[][] second, float[][] third, float[][] fourth, float r, float g, float b) {
			drawLine(worldToScreenX(first[0][0]), 
	                            worldToScreenY(first[1][0]),
	                            worldToScreenZ(first[2][0]), 
	                            worldToScreenY(second[0][0]),
	              		worldToScreenX(second[1][0]), 
	             			worldToScreenZ(second[2][0]),
	             			r, g, b);
			drawLine(worldToScreenX(second[0][0]), 
	                            worldToScreenY(second[1][0]),
	                            worldToScreenZ(second[2][0]), 
	                            worldToScreenY(fourth[0][0]),
	              		worldToScreenX(fourth[1][0]), 
	             			worldToScreenZ(fourth[2][0]),
	             			r, g, b);
			drawLine(worldToScreenX(third[0][0]), 
	                            worldToScreenY(third[1][0]),
	                            worldToScreenZ(third[2][0]), 
	                            worldToScreenY(fourth[0][0]),
	              		worldToScreenX(fourth[1][0]), 
	             			worldToScreenZ(fourth[2][0]),
	             			r, g, b);
			drawLine(worldToScreenX(first[0][0]), 
	                            worldToScreenY(first[1][0]),
	                            worldToScreenZ(first[2][0]), 
	                            worldToScreenY(third[0][0]),
	              		worldToScreenX(third[1][0]), 
	             			worldToScreenZ(third[2][0]),
	             			r, g, b);
			
		}
		private void drawSolidCube(float[][] solidCubePoint1, float[][] solidCubePoint2, float[][] solidCubePoint3, float[][] solidCubePoint4,
				float[][] solidCubePoint5, float[][] solidCubePoint6, float[][] solidCubePoint7, float[][] solidCubePoint8, float r, float g, float b) {
			//front
			drawSquareTriangle(solidCubePoint1, solidCubePoint2, solidCubePoint3, solidCubePoint4, r, g ,b);
			//back
			drawSquareTriangle(solidCubePoint5, solidCubePoint6, solidCubePoint7, solidCubePoint8, r, g, b);
			//sides
			drawSquareTriangle(solidCubePoint1, solidCubePoint5, solidCubePoint3, solidCubePoint7, r, g, b);
			drawSquareTriangle(solidCubePoint2, solidCubePoint6, solidCubePoint4, solidCubePoint8, r, g, b);
			//top
			drawSquareTriangle(solidCubePoint1, solidCubePoint2, solidCubePoint5, solidCubePoint6, r, g, b);
			//bottom
			drawSquareTriangle(solidCubePoint3, solidCubePoint4, solidCubePoint7, solidCubePoint8, r, g, b);
//			//front
//			drawSquareLine(solidCubePoint1, solidCubePoint2, solidCubePoint3, solidCubePoint4, r, g ,b);
//			//back
//			drawSquareLine(solidCubePoint5, solidCubePoint6, solidCubePoint7, solidCubePoint8, r, g, b);
//			//sides
//			drawSquareLine(solidCubePoint1, solidCubePoint5, solidCubePoint3, solidCubePoint7, r, g, b);
//			drawSquareLine(solidCubePoint2, solidCubePoint6, solidCubePoint4, solidCubePoint8, r, g, b);
			
		}
		private void drawSquareTriangle(float[][] first, float[][] second, float[][] third, float[][] fourth, float r, float g, float b) {
			drawTriangle(worldToScreenX(first[0][0]), 
	                            worldToScreenY(first[1][0]),
	                            worldToScreenZ(first[2][0]), 
	                            worldToScreenY(second[0][0]),
	              		worldToScreenX(second[1][0]), 
	             			worldToScreenZ(second[2][0]),
	             			worldToScreenY(fourth[0][0]),
	              		worldToScreenX(fourth[1][0]), 
	             			worldToScreenZ(fourth[2][0]),
	             			r, g, b);
			drawTriangle(worldToScreenX(first[0][0]), 
	                            worldToScreenY(first[1][0]),
	                            worldToScreenZ(first[2][0]), 
	                            worldToScreenY(third[0][0]),
	              		worldToScreenX(third[1][0]), 
	             			worldToScreenZ(third[2][0]),
	             			worldToScreenY(fourth[0][0]),
	              		worldToScreenX(fourth[1][0]), 
	             			worldToScreenZ(fourth[2][0]),
	             			r, g, b);
			
		}
	/**
	     * Draws a line given the coordinates if triangle = false and
	     * it just keeps the coordinates of where each pixel would be drawn when
	     * triangle = true (in this case it doesn't draw the pixels, just keeps track
	     * of their locations).
	     * 
	     * @param sx1 X coordinate of the first point.
	     * @param sy1 Y coordinate of the first point.
	     * @param sx2 X coordinate of the second point.
	     * @param sy2 Y coordinate of the second point.
	     * @param triangle Decides whether the method should draw the pixels (false) or just
	     *               keep track of them (true);
	 * @param b 
	 * @param g 
	 * @param r 
	     */
	    private void drawLine(int sx1, int sy1, float sz1, int sx2, int sy2, float sz2, boolean triangle, double r, double g, double b) {
		int red = (int) (r*255);
		int green = (int) (g*255);
		int blue = (int) (b*255);
	        float slope;
	        float slopeZOfX;
	        float slopeZOfY;
	        // avoids division by 0 and sets appropriate slope value
	        if (sx2 - sx1 == 0) {
	            slope = Float.MAX_VALUE;
	            slopeZOfX = (float) (sz2-sz1) / 1;
	            slopeZOfY = (float) (sz2-sz1) / (sy2-sy1);
	        } else {
	            slope = (float) (sy2 - sy1) / (sx2 - sx1); 
	            slopeZOfX = (float) (sz2-sz1) / (sx2-sx1);
	            slopeZOfY = (float) (sz2-sz1) / (sy2-sy1);
	        }	      
	        int start, end;
	        if (slope >= -1 && slope <= 1) {
	            float y;
	            float z;
	            // choose start and end points
	            if (sx1 < sx2) {
	                start = sx1;
	                end = sx2;
	                y = sy1;
	                z = sz1;
	            } else {
	                start = sx2;
	                end = sx1;
	                y = sy2;
	                z = sz2;
	            }
	         
	            for(int x = start; x <= end; x++) {

	                int newY = Math.round(y);
	                if (triangle) {
	                    // just keep track of where pixels would be placed
	              	  
	                    if (coords[newY] == null) {
	                        coords[newY] = new Range(x,x, z, z);
	                    } else {
	              	      if (coords[newY].zValueMin > z) {
		                            coords[newY].zValueMin = z;
		                        } else if (coords[newY].zValueMax < z) {
		                            coords[newY].zValueMax = z;
		                        }  
	                        if (coords[newY].min > x) {
	                            coords[newY].min = x;
	                        } else if (coords[newY].max < x) {
	                            coords[newY].max = x;
	                        }  
	                    }
	                } else {
	              	  zBuffer(x, newY, z, red, green, blue);
//              		  drawPixel(x, newY, color[0], color[1], color[2]);
	                }
	        
	                y += slope;
	                z += slopeZOfX;
	            }
	            
	        } else if (slope > 1  || slope < -1) {
	         
	            float x;
	            float z;
	            // choose start and end points
	            if (sy1 < sy2) {
	                start = sy1;
	                end = sy2;
	                x = sx1;
	                z = sz1;
	            } else {
	                start = sy2;
	                end = sy1;
	                x = sx2;
	                z = sz2;
	            }
	         
	            for(int y = start; y <= end; y++) {

	                int newX = Math.round(x);	            
	                if (triangle) {
	                    // keep track of where the pixels would be placed
	                    if (coords[y] == null) {
	                        coords[y] = new Range(newX, newX, z, z);
	                    } else {
	              	      if (coords[y].zValueMin > z) {
		                            coords[y].zValueMin = z;
		                        } else if (coords[y].zValueMax < z) {
		                            coords[y].zValueMax = z;
		                        }  
	                        if (coords[y].min > newX) {
	                            coords[y].min = newX;
	                        } else if (coords[y].max < newX) {
	                            coords[y].max = newX;
	                        }
	                    }
	                } else {
	              	  zBuffer(newX, y, z, red, green, blue);
//	              	  drawPixel(newX, y, color[0], color[1], color[2]);
	                }
	                x += 1/slope;
	                z += slopeZOfY;
	            }
	        } 
	    }
	private void zBuffer(int x, int y, float z, int r, int g, int b) {
     	  float zBufferValue = zBuffer[x][y][0];
	  if(zBufferValue == Float.MIN_VALUE) {
		  zBuffer[x][y][0] = z;
			drawPixel(x, y, r, g, b);
	  } else if(z > zBufferValue) {
		int red = pixels[(height - y - 1) * width * 3 + x * 3];
		int green = pixels[(height - y - 1) * width * 3 + x * 3 + 1];
		int blue = pixels[(height - y - 1) * width * 3 + x * 3 + 2];
//		System.out.println("Replace "+red+", "+green+", "+blue+" with "+
//				r+", "+g+", "+b+" at "+
//				" ("+x+", "+y+", "+z+")");
		drawPixel(x, y, r, g, b);
	  } else {
//			int r = pixels[(height - y - 1) * width * 3 + x * 3];
//			int g = pixels[(height - y - 1) * width * 3 + x * 3 + 1];
//			int b = pixels[(height - y - 1) * width * 3 + x * 3 + 2];
//			System.out.println("Not Replacing "+r+", "+g+", "+b+" with "+
//					color[0]+", "+color[1]+", "+color[2]+" at "+
//					" ("+x+", "+y+", "+z+")");
		  zBuffer[x][y][0] = z;
	  }
	}
	public void paintComponent(Graphics g) {
		try {
			createImage();
		} catch (IOException e) {
			e.printStackTrace();
		}
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		WritableRaster raster = image.getRaster();
		raster.setPixels(0, 0, width, height, pixels);
		g.drawImage(image, 0, 0, null);
	}
	public void keyPressed(KeyEvent e) {
		switch(e.getKeyCode()) {
			case KeyEvent.VK_LEFT:
				rotateYValue += 1.0;
				break;
			case KeyEvent.VK_RIGHT:
				rotateYValue -= 1.0;
				break;
			case KeyEvent.VK_UP:
				rotateXValue += 1.0;
				break;
			case KeyEvent.VK_DOWN:
				rotateXValue -= 1.0;
				break;
			case KeyEvent.VK_P:
				break;
			case KeyEvent.VK_O:
				break;
			case KeyEvent.VK_L:
				if(e.isShiftDown()) {
					left -= 10;
				} else {
					left += 10;
				}
				break;
			case KeyEvent.VK_R:
				if(e.isShiftDown()) {
					right -= 10;
				} else {
					right += 10;
				}
				break;
			case KeyEvent.VK_T:
				if(e.isShiftDown()) {
					top -= 10;
				} else {
					top += 10;
				}
				break;
			case KeyEvent.VK_B:
				if(e.isShiftDown()) {
					bottom -= 10;
				} else {
					bottom += 10;
				}
				break;
			case KeyEvent.VK_N:
				if(e.isShiftDown()) {
					near -= 10;
				} else {
					near += 10;
				}
				break;
			case KeyEvent.VK_F:
				if(e.isShiftDown()) {
					far -= 10;
				} else {
					far += 10;
				}
				break;
				
		}
		repaint();
	}
	public void keyReleased(KeyEvent e) {
//		switch(e.getKeyCode()) {
//			case KeyEvent.VK_LEFT:
//				rotateYValue += 1.0;
//				break;
//			case KeyEvent.VK_RIGHT:
//				rotateYValue -= 1.0;
//				break;
//			case KeyEvent.VK_UP:
//				rotateXValue += 1.0;
//				break;
//			case KeyEvent.VK_DOWN:
//				rotateXValue -= 1.0;
//				break;
//			case KeyEvent.VK_P:
//				break;
//			case KeyEvent.VK_O:
//				break;
//			case KeyEvent.VK_L:
//				if(e.isShiftDown()) {
//					left -= 10;
//				} else {
//					left += 10;
//				}
//				break;
//			case KeyEvent.VK_R:
//				if(e.isShiftDown()) {
//					right -= 10;
//				} else {
//					right += 10;
//				}
//				break;
//			case KeyEvent.VK_T:
//				if(e.isShiftDown()) {
//					top -= 10;
//				} else {
//					top += 10;
//				}
//				break;
//			case KeyEvent.VK_B:
//				if(e.isShiftDown()) {
//					bottom -= 10;
//				} else {
//					bottom += 10;
//				}
//				break;
//			case KeyEvent.VK_N:
//				if(e.isShiftDown()) {
//					near -= 10;
//				} else {
//					near += 10;
//				}
//				break;
//			case KeyEvent.VK_F:
//				if(e.isShiftDown()) {
//					far -= 10;
//				} else {
//					far += 10;
//				}
//				break;
//				
//		}
//		repaint();
	}
	public void keyTyped(KeyEvent e) {
	}
	public static void main(String args[]) {
		JFrame frame = new JFrame("Paint Demo");
		if(file.equals("")) {
			final JFileChooser fc = new JFileChooser();
			fc.showOpenDialog(null);
			final File path = fc.getSelectedFile();
			file = path.getAbsolutePath();
		}
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().add(new ProjectTwoMilestoneOne());
		frame.pack();
		frame.setLocationRelativeTo(null);
		frame.setVisible(true);
	}
}